classdef ParseRawSBE25Data < ParseRawSBEData
  % Parser for raw (hexadecimal) flight (SBE-25) data
  % Derived from ParseRawSBEData
  % C-tor sig: p = ParseRawSBE25Data(dataFilename, xmlconFilename); 
  %                args optional
  %    dataFilename: path of hex data file
  %    xmlconFilename: path of xmlcon file
  %
  % Author: David Goldsmith, Wash. State Dept. of Ecology, dgol461@ecy.wa.gov
  % Release date: 9/15/2011
  %
  % An OO implemetation--based on the SBE-25 documentation, with parts 
  % borrowed from IMOS' SBE Parsers--of the Sea-Bird hexadecimal raw data 
  % translation protocol quoted below.
  %
  % From the "SBE 25 Sealogger CTD User's Manual, Version #017, 12/29/2009," pg. 39
  %
  % "Eight to nineteen bytes are stored per CTD scan, depending on the 
  % number of ... auxiliary sensors enabled.  The format is:
  % Aux. Sens. Enabled  |    Data Format
  %------------------------------------------------------------------------
  %         0           |    ttttttccccccsppp
  %         1           |    ttttttccccccsppp0uuu
  %         2           |    ttttttccccccspppuuuvvv
  %         3           |    ttttttccccccspppuuuvvv0www
  %         4           |    ttttttccccccspppuuuvvvwwwxxx
  %         5           |    ttttttccccccspppuuuvvvwwwxxx0yyy
  %         6           |    ttttttccccccspppuuuvvvwwwxxxyyyzzz
  %         7           |    ttttttccccccspppuuuvvvwwwxxxyyyzzz0aaa
  %
  % where
  % tttttt = temperature frequency, 3 bytes
  % cccccc = conductivity frequency, 3 bytes
  % s = sign character for pressure (0 = +, 4 = -)
  % ppp = pressure, 12 bits
  % uuu = stored voltage output 0, 12 bits
  % vvv = stored voltage output 1, 12 bits
  % www = stored voltage output 2, 12 bits
  % xxx = stored voltage output 3, 12 bits
  % yyy = stored voltage output 4, 12 bits
  % zzz = stored voltage output 5, 12 bits
  % aaa = stored voltage output 6, 12 bits
  % 0 = zero, used to provide an even number of characters, 4 bits
  %
  % Calculation of each parameter from the data is described below 
  % (use the decimal equivalent of the hex data in the equations).
  % Frequency (temperature or conductivity) = (byte0 * 256) + byte1 + (byte2 / 256)
  % pressure* (corrected A/D counts) = ppp
  % voltage = uuu / 819 (similar form for all voltage auxiliary sensors)
  %
  % Example: SBE 25 with 2 external voltages sampled,
  % example scan = ttttttccccccspppuuuvvv = 1FE780281D1904293F2D1E
  %
  % Temperature = tttttt = 1FE780 (1F = 31 decimal, E7 = 231 decimal, 80 = 128 decimal);
  % temperature frequency = (31 * 256) + 231 + (128 / 256) = 8167.500 Hz
  %
  % Conductivity = cccccc = 281D19 (28 = 40 decimal, 1D = 29 decimal, 19 = 25 decimal);
  % conductivity frequency = (40 *256) + 29 + (25 / 256) = 10269.098 Hz
  %
  % Strain-gauge pressure= sppp = 0429 Pressure sign character s= 0 (+)
  % corrected A/D counts ppp = 429 (1065 decimal);
  % corrected pressure A/D counts = + 1065
  %
  % First external voltage = uuu = 3F2 (1010 decimal);
  % voltage = 1010 / 819 = 1.233 volts
  % Second external voltage = vvv = D1E (3358 decimal);
  % voltage = 3358 / 819 = 4.100 volts
  %
  % * Note regarding pressure calculation: rounded to the nearest 1e-4 (one decimal place of
  % precision greater than SBE's output for pressure), our computed pressure is within "round-off
  % noise" of SBE's, albeit with a better match between the psiG results.

    properties 
      % inherited from ParseData
      %     dataFilename
      % inherited from ParseRawSBEData
      %     xmlconFilename  = ''; full (perhaps relative) path of CTD xmlcon file
      %     instHeaderLines = {}; cell array of strings, from the header, containing instrument data
      %                           in the file these lines are preceded by a '*'
      %     procHeaderLines = {}; cell array of strings, from the header, containing processed data
      %                           in the file these lines are preceded by a '#'
      %     dataLines       = {}; cell array of strings, from the "body" of the file, 
      %                           containing the raw data; these lines are
      %                           preceded by neither of the above
        order = 0;
        Sensors = struct;
        instHdrFldTokMap = struct(...% structure containing mapping of parsing tokens to instHeaderData field names
                                  'modelNo', 'SBE', ...
                                  'dataOrig', 'FileName =', ...
                                  'softwareVers', 'Software Version', ...
                                  'tempSN', 'Temperature SN =', ...
                                  'condSN', 'Conductivity SN =', ...
                                  'sysUpldTime', 'UpLoad Time =', ...
                                  'ctdVers', 'SBE 25 CTD  V', ...
                                  'extPresSensRng', 'external pressure sensor, range =', ...
                                  'extPresSensTC', 'tcval =', ...
                                  'xtal', 'xtal =', ...
                                  'clk', 'clk =', ...
                                  'vMain', 'vmain =', ...
                                  'iop', 'iop =', ...
                                  'vLith', 'vlith =', ...
                                  'nCasts', 'ncasts =', ...
                                  'samples', 'samples =', ...
                                  'free', 'free =', ...
                                  'lWait', 'lwait = ', ...
                                  'scnsAvgd', 'scans averaged =', ...
                                  'scanRate', 'stored at', ...
                                  'xmitRate', 'transmitted at', ...
                                  'pumpOnThrshld', 'pump turn on =', ...
                                  'pumpDelay', 'pump delay =', ...
                                  'batType', 'battery type =', ...
                                  'extV', 'voltage # ', ...
                                  'castNo', 'cast ', ...
                                  'firstSampleNo', 'smpls ', ...
                                  'lastSampleNo', 'to ', ...
                                  'nV', 'nv =', ...
                                  'avg', 'avg =', ...
                                  'stp', 'stp =');
        Tf; % Temperature
        Cf; % Conductivity
        Pn; % Pressure
        rV; % External Sensors (variable #).  Typical column # associations 
            % are: 1=DO, 2=pH, 3=Transmissiometer, 4=Fluorometer, 
            % 5=Turbidity, 6=PAR, 7=unused
    end

    methods
        function parser = ParseRawSBE25Data(dataFilename, xmlconFilename) % Constructor
            if nargin < 1, dataFilename = ''; end
            if nargin < 2, xmlconFilename = ''; end
            parser = parser@ParseRawSBEData(dataFilename, xmlconFilename);
            if ~isempty(dataFilename), parser.readDataFile(); end
            if ~isempty(xmlconFilename), parser.readConfigFiles(); end
        end
        
        function parseInstHeader(obj)
            global instHeaderData
            flds = fields(obj.instHdrFldTokMap);
            for i=1:length(flds)
                start = regexp(obj.instHeaderLines, obj.instHdrFldTokMap.(flds{i}));
                start = start(1);
                if strcmp(obj.instHdrFldTokMap.(flds{i}), 'stp =')
                    tmp = obj.instHeaderLines(start:end);
                else
                    finis = regexp(obj.instHeaderLines, obj.instHdrFldTokMap.(flds{i+1}));
                    finis = finis(1) - 1;
                    tmp = obj.instHeaderLines(start:finis);
                end
                tmp = strtrim(strrep(tmp, obj.instHdrFldTokMap.(flds{i}), ''));
                switch flds{i}
                    case {'dataOrig', 'stp'}
                        val = tmp;
                    case 'ctdVers'
                        words = allwords(tmp, ' ');
                        val = words{1};
                        instHeaderData.SN = words{3};
                        instHeaderData.SN;
                        instHeaderData.someDatetime = datenum([words{4} ' ' words{5}]);
                    case 'batType'
                        cut = regexp(tmp, '\d');
                        val = tmp(1:cut-1);
                        instHeaderData.extVSamp = extract_leading_number(tmp(cut:end));
                    case 'castNo'
                        [tok, rem] = strtok(tmp, '/');
                        val = extract_leading_number(tok(1:end-2));
                        castDT = [tok(end-2:end) rem(1:3) '/' year ' ' rem(4:end)];
                        instHeaderData.castDatetime = datenum(castDT);
                        datestr(instHeaderData.castDatetime);
                    case 'sysUpldTime'
                        try
                            val = datenum(tmp);
                            year = datestr(val, 'yyyy');
                        catch ME1
                            try
                                val = datenum(strtrim(strtok(tmp,'(')));
                                year = datestr(val, 'yyyy');
                            catch ME2
                                val = ['ALERT: unhandled exceptions "'...
                                       ME1.message '" followed by "'...
                                       ME2.message '"in ParseRawSBE25Data.'...
                                       'parseInstHeader, case sysUpldTime'];
                                year = '';
                            end
                        end
                    case 'extV'
                        val = 1:instHeaderData.extVSamp;
                        for j=val
                            tmp = tmp(regexp(tmp, '\d+')+1 : end); % throw away the odd numbered numbers
                            cut = regexp(tmp, '\d+'); cut = cut(1);
                            tmp = tmp(cut:end);
                            [val(j), tmp] = extract_leading_number(tmp);
                        end
                    otherwise
                        val = extract_leading_number(tmp);
                end
                instHeaderData.(flds{i}) = val;
            end
          % Now add new header info
            instHeaderData.BadFlag = NaN;
            instHeaderData.Datcnv_date = utcnow();
        end
        
        function parseProcHeader(obj)
            for line = obj.procHeaderLines
            end
        end
        
        function unpackConfigDat(obj)
            global instHeaderData xmlconData
            if isfield(xmlconData, 'Instrument_SensorArray_Sensor') && ...
               iscell(xmlconData.Instrument_SensorArray_Sensor)
                xmlconData.('Instrument_SensorArray') = struct;
                for c=xmlconData.Instrument_SensorArray_Sensor(:)'
                    tmp = c{1};
                    if isstruct(tmp)
                        structs = {}; nstructs = {};
                        fields = fieldnames(tmp);
                        for field=fields(:)'
                            f = char(field);
                            if isstruct(tmp.(f))
                                structs{end+1} = tmp.(f);
                            else
                                nstructs{end+1} = f;
                            end
                        end
                        for n=nstructs(:)'
                            f = char(n);
                            for S=structs(:)'
                                s = S{1};
                                fields = fieldnames(s);
                                if ismember(f, fields) && ...
                                   isequal(tmp.(f), s.(f))
                                    tmp = rmfield(tmp, f);
                                else
                                    s.(f) = tmp.(f);
                                    tmp = rmfield(tmp, f);
                                end
                            end
                        end
                        fields = fieldnames(tmp);
                        f = char(fields{1});
                        xmlconData.Instrument_SensorArray.(f) = s;
                    end
                end
                xmlconData = rmfield(xmlconData, 'Instrument_SensorArray_Sensor');
                fields = fieldnames(xmlconData.Instrument_SensorArray);
                for field=fields(:)'
                    f = char(field);
                    [xmlconData.Instrument_SensorArray.(f),~] = ...
                            flattenStruct({xmlconData.Instrument_SensorArray.(f)});
                end
                obj.Sensors = xmlconData.Instrument_SensorArray;
                xmlconData = rmfield(xmlconData, 'Instrument_SensorArray');
            end
          % Assign values historically obtained from psa files
            instHeaderData.('skip') = 0;
            instHeaderData.('outType') = 'DB';
        end
        
        function convertHexData(obj)
            global instHeaderData 
            N = numel(obj.dataLines);
            extV = instHeaderData.nV;
            hexForm = 'ttttttccccccsppp';
            if extV
                hexForm = [hexForm '0uuu'];
            end
            if extV > 1
                hexForm = [hexForm(1:end-4) hexForm(end-2:end) 'vvv'];
            end
            if extV > 2
                hexForm = [hexForm '0www'];
            end
            if extV > 3
                hexForm = [hexForm(1:end-4) hexForm(end-2:end) 'xxx'];
            end
            if extV > 4
                hexForm = [hexForm '0yyy'];
            end
            if extV > 5
                hexForm = [hexForm(1:end-4) hexForm(end-2:end) 'zzz'];
            end
            if extV > 6
                hexForm = [hexForm '0aaa'];
            end
            obj.Tf = zeros(N,1); obj.Cf = obj.Tf; obj.Pn = obj.Tf;
            obj.rV = zeros(N, extV);
            
            n = 1;
            codes = 'uvwxyza';
            for lin = obj.dataLines
                line = lin{:};
                try
                    tmp = line(hexForm=='t');
                    obj.Tf(n) = 256*hex2dec(tmp(1:2)) + hex2dec(tmp(3:4)) + hex2dec(tmp(5:6))/256;

                    tmp = line(hexForm=='c');
                    obj.Cf(n) = 256*hex2dec(tmp(1:2)) + hex2dec(tmp(3:4)) + hex2dec(tmp(5:6))/256;

                    sgn = 1 - str2double(line(hexForm=='s'))/2;
                    tmp = line(hexForm=='p');
                    obj.Pn(n) = sgn * (256*hex2dec(tmp(1)) + 16*hex2dec(tmp(2)) + hex2dec(tmp(3)));

                    for i=1:extV
                        tmp = line(hexForm==codes(i));
                        obj.rV(n,i) = (256*hex2dec(tmp(1)) + 16*hex2dec(tmp(2)) + hex2dec(tmp(3)))/819;
                    end
                catch
                end
                n = n + 1;
            end
        end
        
    end
end
